<!DOCTYPE html>
<html lang="zh-cn" dir="ltr">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Go语言相关 # GMP模型 # G goroutine协程
P processor处理器
M thread线程
Processor 它包含了运行goroutine的资源，如果线程想运行goroutine,必须先获取P，P中还包含了可运行的G队列。
在Go中，线程是运行goroutine的实体，调度器的功能是把可运行的goroutine分配到工作线程上。
全局队列：存放等待运行的G P的本地队列：同全局队列类似，存放的也是等待运行的G，存的数量有限，不超过256个。新建 G’时，G’优先加入到 P 的本地队列，如果队列满了，则会把本地队列中一半的 G 移动到全局队列。 P列表：所有的P都在程序启动时创建，并保存在数组中，最多有 GOMAXPROCS(可配置) 个。 M：线程想运行任务就得获取P，从P的本地队列获取G，P队列为空时，M 也会尝试从全局队列拿一批 G 放到 P 的本地队列，或从其他 P 的本地队列偷一半放到自己 P 的本地队列。M 运行 G，G 执行之后，M 会从 P 获取下一个 G，不断重复下去。 Goroutine 调度器和 OS 调度器是通过 M 结合起来的，每个 M 都代表了 1 个内核线程，OS 调度器负责把内核线程分配到 CPU 的核上执行。
P和M的个数问题 # 1、P的数量：
由启动时环境变量 $GOMAXPROCS 或者是由 runtime 的方法 GOMAXPROCS() 决定。这意味着在程序执行的任意时刻都只有 $GOMAXPROCS 个 goroutine 在同时运行。 2、M的数量：
go 语言本身的限制：go 程序启动时，会设置 M 的最大数量，默认 10000.">
<meta name="theme-color" content="#FFFFFF">
<meta name="color-scheme" content="light dark"><meta property="og:title" content="go语言底层基础" />
<meta property="og:description" content="Go语言相关 # GMP模型 # G goroutine协程
P processor处理器
M thread线程
Processor 它包含了运行goroutine的资源，如果线程想运行goroutine,必须先获取P，P中还包含了可运行的G队列。
在Go中，线程是运行goroutine的实体，调度器的功能是把可运行的goroutine分配到工作线程上。
全局队列：存放等待运行的G P的本地队列：同全局队列类似，存放的也是等待运行的G，存的数量有限，不超过256个。新建 G’时，G’优先加入到 P 的本地队列，如果队列满了，则会把本地队列中一半的 G 移动到全局队列。 P列表：所有的P都在程序启动时创建，并保存在数组中，最多有 GOMAXPROCS(可配置) 个。 M：线程想运行任务就得获取P，从P的本地队列获取G，P队列为空时，M 也会尝试从全局队列拿一批 G 放到 P 的本地队列，或从其他 P 的本地队列偷一半放到自己 P 的本地队列。M 运行 G，G 执行之后，M 会从 P 获取下一个 G，不断重复下去。 Goroutine 调度器和 OS 调度器是通过 M 结合起来的，每个 M 都代表了 1 个内核线程，OS 调度器负责把内核线程分配到 CPU 的核上执行。
P和M的个数问题 # 1、P的数量：
由启动时环境变量 $GOMAXPROCS 或者是由 runtime 的方法 GOMAXPROCS() 决定。这意味着在程序执行的任意时刻都只有 $GOMAXPROCS 个 goroutine 在同时运行。 2、M的数量：
go 语言本身的限制：go 程序启动时，会设置 M 的最大数量，默认 10000." />
<meta property="og:type" content="article" />
<meta property="og:url" content="http://example.org/docs/golang/%E5%9F%BA%E7%A1%80/go%E8%AF%AD%E8%A8%80%E5%BA%95%E5%B1%82%E5%9F%BA%E7%A1%80/" /><meta property="article:section" content="docs" />
<meta property="article:published_time" content="2022-08-03T10:46:26+00:00" />
<meta property="article:modified_time" content="2022-08-03T10:46:26+00:00" />

<title>go语言底层基础 | Soulmate</title>
<link rel="manifest" href="/manifest.json">
<link rel="icon" href="/favicon.png" type="image/x-icon">
<link rel="stylesheet" href="/book.min.c58292d36b18b675680ab9baea2029204537b839ea72f258746ec0f32ce8d6c8.css" integrity="sha256-xYKS02sYtnVoCrm66iApIEU3uDnqcvJYdG7A8yzo1sg=" crossorigin="anonymous">
  <script defer src="/flexsearch.min.js"></script>
  <script defer src="/en.search.min.7d4a2df4b0464655ec51cd667723b235b8c0102457ac51664365cdfb0a14ab66.js" integrity="sha256-fUot9LBGRlXsUc1mdyOyNbjAECRXrFFmQ2XN&#43;woUq2Y=" crossorigin="anonymous"></script>
<!--
Made with Book Theme
https://github.com/alex-shpak/hugo-book
-->
  
</head>
<body dir="ltr">
  <input type="checkbox" class="hidden toggle" id="menu-control" />
  <input type="checkbox" class="hidden toggle" id="toc-control" />
  <main class="container flex">
    <aside class="book-menu">
      <div class="book-menu-content">
        
  <nav>
<h2 class="book-brand">
  <a class="flex align-center" href="/"><img src="/logo.png" alt="Logo" /><span>Soulmate</span>
  </a>
</h2>


<div class="book-search">
  <input type="text" id="book-search-input" placeholder="Search" aria-label="Search" maxlength="64" data-hotkeys="s/" />
  <div class="book-search-spinner hidden"></div>
  <ul id="book-search-results"></ul>
</div>












  



  
  <ul>
    
      
        <li class="book-section-flat" >
          
  
  

  
    <input type="checkbox" id="section-074eed0fb8c6f9d99dfeff898388792f" class="toggle"  />
    <label for="section-074eed0fb8c6f9d99dfeff898388792f" class="flex justify-between">
      <a role="button" class="">计算机基础</a>
    </label>
  

          
  <ul>
    
      
        <li>
          
  
  

  
    <a href="/docs/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/git%E5%9F%BA%E7%A1%80/" class="">Git基础</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/linux%E5%9F%BA%E7%A1%80/" class="">Linux基础</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%9F%BA%E7%A1%80/" class="">操作系统基础</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%9F%BA%E7%A1%80/" class="">数据库基础</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%9F%BA%E7%A1%80/" class="">计算机网络基础</a>
  

        </li>
      
    
  </ul>

        </li>
      
    
      
        <li class="book-section-flat" >
          
  
  

  
    <span>Golang</span>
  

          
  <ul>
    
      
        <li>
          
  
  

  
    <input type="checkbox" id="section-670eda717a06f31447c52422ec93a159" class="toggle" checked />
    <label for="section-670eda717a06f31447c52422ec93a159" class="flex justify-between">
      <a role="button" class="">基础</a>
    </label>
  

          
  <ul>
    
      
        <li>
          
  
  

  
    <a href="/docs/golang/%E5%9F%BA%E7%A1%80/go%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80%E4%B8%89/" class="">go语言基础（三）</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/golang/%E5%9F%BA%E7%A1%80/2021-12-27-gin%E6%A1%86%E6%9E%B6/" class="">gin框架</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/golang/%E5%9F%BA%E7%A1%80/beego%E6%A1%86%E6%9E%B6/" class="">Beego框架</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/golang/%E5%9F%BA%E7%A1%80/go%E8%AF%AD%E8%A8%80%E5%BA%95%E5%B1%82%E5%9F%BA%E7%A1%80/" class="active">go语言底层基础</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/golang/%E5%9F%BA%E7%A1%80/2022-05-24-gorm%E6%80%BB%E7%BB%93/" class="">Gorm总结</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/golang/%E5%9F%BA%E7%A1%80/2021-12-20-benchmark%E6%B5%8B%E8%AF%95/" class="">benchmark测试</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/golang/%E5%9F%BA%E7%A1%80/2021-04-07-go%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80%E4%B8%80/" class="">2021 04 07 Go语言基础（一）</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/golang/%E5%9F%BA%E7%A1%80/2021-10-26-go%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80%E4%BA%8C/" class="">2021 10 26 Go语言基础（二）</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/golang/%E5%9F%BA%E7%A1%80/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-go/" class="">数据结构 Go</a>
  

        </li>
      
    
  </ul>

        </li>
      
    
      
        <li>
          
  
  

  
    <input type="checkbox" id="section-c453881a62c640f6a4cceec99c02477e" class="toggle"  />
    <label for="section-c453881a62c640f6a4cceec99c02477e" class="flex justify-between">
      <a role="button" class="">高阶</a>
    </label>
  

          
  <ul>
    
      
        <li>
          
  
  

  
    <a href="/docs/golang/%E9%AB%98%E9%98%B6/go%E8%AF%AD%E8%A8%80%E9%AB%98%E9%98%B6%E4%B8%80/" class="">Go语言高阶（一）</a>
  

        </li>
      
    
  </ul>

        </li>
      
    
      
        <li>
          
  
  

  
    <input type="checkbox" id="section-ee53da12118bfd3e9b35f3e9494ab55b" class="toggle"  />
    <label for="section-ee53da12118bfd3e9b35f3e9494ab55b" class="flex justify-between">
      <a role="button" class="">LeetCode</a>
    </label>
  

          
  <ul>
    
      
        <li>
          
  
  

  
    <a href="/docs/golang/leetcode/2021-10-14-golang%E5%8A%9B%E6%89%A3%E5%88%B7%E9%A2%98%E4%B8%80/" class="">golang力扣刷题（一）</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/golang/leetcode/2021-11-04-golang%E5%8A%9B%E6%89%A3%E5%88%B7%E9%A2%98%E4%BA%8C/" class="">golang力扣刷题（二）</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/golang/leetcode/%E5%BF%85%E5%88%B7top101/" class="">必刷top101</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/golang/leetcode/2021-10-28-leetcode%E7%AE%97%E6%B3%95%E6%80%BB%E7%BB%93/" class="">LeetCode算法总结</a>
  

        </li>
      
    
  </ul>

        </li>
      
    
  </ul>

        </li>
      
    
      
        <li class="book-section-flat" >
          
  
  

  
    <span>区块链</span>
  

          
  <ul>
    
      
        <li>
          
  
  

  
    <input type="checkbox" id="section-76bfb65f46ce25a0ff78d6cc4ad47773" class="toggle"  />
    <label for="section-76bfb65f46ce25a0ff78d6cc4ad47773" class="flex justify-between">
      <a role="button" class="">Fabric</a>
    </label>
  

          
  <ul>
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric/2021-05-08-fabric-sdk-go%E8%AF%A6%E8%A7%A3/" class="">fabric-sdk-go详解</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <input type="checkbox" id="section-1e3df3eda5cf08a9c7e8cc723288fefb" class="toggle"  />
    <label for="section-1e3df3eda5cf08a9c7e8cc723288fefb" class="flex justify-between">
      <a role="button" class="">环境测试</a>
    </label>
  

          
  <ul>
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric/%E7%8E%AF%E5%A2%83%E6%B5%8B%E8%AF%95/2021-03-18-centos%E5%AE%89%E8%A3%85fabric1.2/" class="">centos安装fabric1.2</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric/%E7%8E%AF%E5%A2%83%E6%B5%8B%E8%AF%95/2021-03-22-fabric%E7%BD%91%E7%BB%9C%E4%B8%AD%E7%9A%84%E6%8A%A5%E9%94%99%E4%BA%8C/" class="">2021 03 22 Fabric网络中的报错（二）</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric/%E7%8E%AF%E5%A2%83%E6%B5%8B%E8%AF%95/2021-03-24-fabric%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/" class="">2021 03 24 Fabric环境搭建</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric/%E7%8E%AF%E5%A2%83%E6%B5%8B%E8%AF%95/2021-03-25-fabric-solo%E8%8A%82%E7%82%B9%E6%B5%8B%E8%AF%95/" class="">2021 03 25 Fabric Solo节点测试</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric/%E7%8E%AF%E5%A2%83%E6%B5%8B%E8%AF%95/2021-03-25-fabric%E5%A4%9A%E6%9C%BA%E6%90%AD%E5%BB%BA/" class="">2021 03 25 Fabric多机搭建</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric/%E7%8E%AF%E5%A2%83%E6%B5%8B%E8%AF%95/2021-05-01-%E6%89%8B%E5%8A%A8%E7%94%9F%E6%88%90ca%E8%AF%81%E4%B9%A6%E6%90%AD%E5%BB%BAfabric%E7%BD%91%E7%BB%9C/" class="">2021 05 01 手动生成ca证书搭建fabric网络</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric/%E7%8E%AF%E5%A2%83%E6%B5%8B%E8%AF%95/2021-12-20-%E9%83%A8%E7%BD%B2tape%E6%B5%8B%E8%AF%95/" class="">2021 12 20 部署tape测试</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric/%E7%8E%AF%E5%A2%83%E6%B5%8B%E8%AF%95/fabric%E7%BD%91%E7%BB%9C%E4%B8%AD%E7%9A%84%E6%8A%A5%E9%94%99%E4%B8%80/" class="">Fabric网络中的报错（一）</a>
  

        </li>
      
    
  </ul>

        </li>
      
    
      
        <li>
          
  
  

  
    <input type="checkbox" id="section-25aef1d59a561fcefcaecb043ef8afd2" class="toggle"  />
    <label for="section-25aef1d59a561fcefcaecb043ef8afd2" class="flex justify-between">
      <a role="button" class="">配置文件</a>
    </label>
  

          
  <ul>
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric/%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6/2021-03-29-configtx-yaml%E6%96%87%E4%BB%B6%E8%AF%A6%E8%A7%A3/" class="">2021 03 29 Configtx Yaml文件详解</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric/%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6/2021-03-29-crypto-config-yaml%E6%96%87%E4%BB%B6%E8%AF%A6%E8%A7%A3/" class="">2021 03 29 Crypto Config Yaml文件详解</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric/%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6/2021-03-30-config-yaml%E6%96%87%E4%BB%B6%E8%AF%A6%E8%A7%A3/" class="">2021 03 30 Config Yaml文件详解</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric/%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6/2021-03-30-docker-compose-yaml%E6%96%87%E4%BB%B6%E8%AF%A6%E8%A7%A3/" class="">2021 03 30 Docker Compose Yaml文件详解</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric/%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6/2021-05-01-cryptogen%E7%94%9F%E6%88%90%E7%9A%84%E8%AF%81%E4%B9%A6%E8%AF%A6%E8%A7%A3/" class="">2021 05 01 Cryptogen生成的证书详解</a>
  

        </li>
      
    
  </ul>

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric/2022-04-14-%E5%8D%87%E7%BA%A7%E9%93%BE%E7%A0%81/" class="">升级链码</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric/2021-04-15-fabric-ca%E8%AF%A6%E8%A7%A3/" class="">2021 04 15 Fabric Ca详解</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric/2021-04-15-fabric1.4%E5%A4%9A%E9%80%9A%E9%81%93%E5%AE%9E%E9%AA%8C/" class="">2021 04 15 Fabric1.4多通道实验</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric/2021-04-17-%E5%A6%82%E4%BD%95%E5%9C%A8%E5%B7%B2%E6%9C%89%E7%BB%84%E7%BB%87%E4%B8%AD%E5%A2%9E%E5%8A%A0%E8%8A%82%E7%82%B9/" class="">2021 04 17 如何在已有组织中增加节点</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric/2021-05-02-fabric%E6%B5%8F%E8%A7%88%E5%99%A8%E6%90%AD%E5%BB%BA/" class="">2021 05 02 Fabric浏览器搭建</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric/2021-05-10-%E6%99%BA%E8%83%BD%E5%90%88%E7%BA%A6/" class="">2021 05 10 智能合约</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric/2022-02-25-fabric%E7%9B%B8%E5%85%B3%E6%9C%BA%E5%88%B6%E4%B8%8E%E5%8E%9F%E7%90%86/" class="">2022 02 25 Fabric相关机制与原理</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/fabric/2022-03-25-%E5%8C%BA%E5%9D%97%E9%93%BE%E7%BD%91%E7%BB%9C%E6%B7%BB%E5%8A%A0%E7%BB%84%E7%BB%87/" class="">2022 03 25 区块链网络添加组织</a>
  

        </li>
      
    
  </ul>

        </li>
      
    
      
        <li>
          
  
  

  
    <input type="checkbox" id="section-b07621083aa30b12de4c0333941e479f" class="toggle"  />
    <label for="section-b07621083aa30b12de4c0333941e479f" class="flex justify-between">
      <a role="button" class="">比特币</a>
    </label>
  

          
  <ul>
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/%E6%AF%94%E7%89%B9%E5%B8%81/2022-02-25-%E6%AF%94%E7%89%B9%E5%B8%81%E7%9B%B8%E5%85%B3%E6%9C%BA%E5%88%B6%E4%B8%8E%E5%8E%9F%E7%90%86/" class="">2022 02 25 比特币相关机制与原理</a>
  

        </li>
      
    
  </ul>

        </li>
      
    
      
        <li>
          
  
  

  
    <input type="checkbox" id="section-ff5477999ef29208270c84e8b56b2758" class="toggle"  />
    <label for="section-ff5477999ef29208270c84e8b56b2758" class="flex justify-between">
      <a role="button" class="">IPFS</a>
    </label>
  

          
  <ul>
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/ipfs/2021-06-02-ipfs%E7%A7%81%E6%9C%89%E7%BD%91%E7%BB%9C%E6%90%AD%E5%BB%BA/" class="">2021 06 02 IP Fs私有网络搭建</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/ipfs/2021-07-08-ipfs%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86%E4%B8%80/" class="">2021 07 08 IP Fs基本原理（一）</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/ipfs/2021-07-12-ipfs-webui%E5%8F%AF%E8%A7%86%E5%8C%96%E5%B7%A5%E5%85%B7%E6%90%AD%E5%BB%BA/" class="">2021 07 12 Ipfs Webui可视化工具搭建</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/ipfs/2021-12-05-go-ipfs-api/" class="">2021 12 05 Go Ipfs API</a>
  

        </li>
      
    
  </ul>

        </li>
      
    
      
        <li>
          
  
  

  
    <input type="checkbox" id="section-77a0a17b06e8d04cfb0fba9952941706" class="toggle"  />
    <label for="section-77a0a17b06e8d04cfb0fba9952941706" class="flex justify-between">
      <a role="button" class="">密码学</a>
    </label>
  

          
  <ul>
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/%E5%AF%86%E7%A0%81%E5%AD%A6/2022-08-15-%E5%8C%BA%E5%9D%97%E9%93%BE%E5%AE%89%E5%85%A8%E5%9F%BA%E7%A1%80/" class="">区块链安全基础</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/%E5%AF%86%E7%A0%81%E5%AD%A6/2021-04-12-%E6%A4%AD%E5%9C%86%E6%9B%B2%E7%BA%BF%E5%8A%A0%E5%AF%86/" class="">椭圆曲线加密</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/%E5%AF%86%E7%A0%81%E5%AD%A6/2021-03-04-%E5%AF%86%E7%A0%81%E5%AD%A6%E5%9F%BA%E7%A1%80/" class="">密码学基础</a>
  

        </li>
      
    
  </ul>

        </li>
      
    
      
        <li>
          
  
  

  
    <input type="checkbox" id="section-2e4d10b323fd668021dbd532575d8790" class="toggle"  />
    <label for="section-2e4d10b323fd668021dbd532575d8790" class="flex justify-between">
      <a role="button" class="">Docker</a>
    </label>
  

          
  <ul>
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/docker/docker%E5%9F%BA%E7%A1%80/" class="">Docker基础</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/docker/dockerfile/" class="">Dockerfile</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/docker/2021-04-30-docker%E5%B8%B8%E7%94%A8%E7%9F%A5%E8%AF%86%E6%80%BB%E7%BB%93/" class="">docker常用知识总结</a>
  

        </li>
      
    
  </ul>

        </li>
      
    
      
        <li>
          
  
  

  
    <input type="checkbox" id="section-70383d7f28a7ae4bf840a844eb61aa16" class="toggle"  />
    <label for="section-70383d7f28a7ae4bf840a844eb61aa16" class="flex justify-between">
      <a role="button" class="">共识算法</a>
    </label>
  

          
  <ul>
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/%E5%85%B1%E8%AF%86%E7%AE%97%E6%B3%95/2022-03-26-raft%E5%85%B1%E8%AF%86%E7%AE%97%E6%B3%95/" class="">Raft共识算法</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8C%BA%E5%9D%97%E9%93%BE/%E5%85%B1%E8%AF%86%E7%AE%97%E6%B3%95/%E5%85%B1%E8%AF%86%E7%AE%97%E6%B3%95%E5%9F%BA%E7%A1%80/" class="">共识算法基础</a>
  

        </li>
      
    
  </ul>

        </li>
      
    
  </ul>

        </li>
      
    
      
        <li class="book-section-flat" >
          
  
  

  
    <span>数据库</span>
  

          
  <ul>
    
      
        <li>
          
  
  

  
    <input type="checkbox" id="section-94f26238d1ca3b5bcd40eadc7a88d726" class="toggle"  />
    <label for="section-94f26238d1ca3b5bcd40eadc7a88d726" class="flex justify-between">
      <a role="button" class="">MySql</a>
    </label>
  

          
  <ul>
    
      
        <li>
          
  
  

  
    <a href="/docs/%E6%95%B0%E6%8D%AE%E5%BA%93/mysql/2021-04-20-mysql%E5%9F%BA%E7%A1%80%E6%80%BB%E7%BB%93/" class="">MySql基础总结</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E6%95%B0%E6%8D%AE%E5%BA%93/mysql/mac%E8%BF%9E%E6%8E%A5%E6%95%B0%E6%8D%AE%E5%BA%93%E6%89%80%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98/" class="">Mac连接数据库所遇到的问题</a>
  

        </li>
      
    
  </ul>

        </li>
      
    
      
        <li>
          
  
  

  
    <input type="checkbox" id="section-01e4ef5e6295826a1eab8f1a7114ec2c" class="toggle"  />
    <label for="section-01e4ef5e6295826a1eab8f1a7114ec2c" class="flex justify-between">
      <a role="button" class="">Redis</a>
    </label>
  

          
  <ul>
    
      
        <li>
          
  
  

  
    <a href="/docs/%E6%95%B0%E6%8D%AE%E5%BA%93/redis/2022-03-21-redis%E9%9B%86%E7%BE%A4%E6%90%AD%E5%BB%BA/" class="">Redis集群搭建</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E6%95%B0%E6%8D%AE%E5%BA%93/redis/2022-03-20-redis%E5%9F%BA%E7%A1%80/" class="">Redis基础</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E6%95%B0%E6%8D%AE%E5%BA%93/redis/2021-05-02-redis%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BB%93/" class="">redis面试总结</a>
  

        </li>
      
    
  </ul>

        </li>
      
    
  </ul>

        </li>
      
    
      
        <li class="book-section-flat" >
          
  
  

  
    <input type="checkbox" id="section-5f7b667081fbae0581cd216f66d5102f" class="toggle"  />
    <label for="section-5f7b667081fbae0581cd216f66d5102f" class="flex justify-between">
      <a role="button" class="">博客</a>
    </label>
  

          
  <ul>
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8D%9A%E5%AE%A2/2022-08-27-%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BAhugo/" class="">个人博客搭建Hugo</a>
  

        </li>
      
    
      
        <li>
          
  
  

  
    <a href="/docs/%E5%8D%9A%E5%AE%A2/%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BAhexo/" class="">个人博客搭建Hexo</a>
  

        </li>
      
    
  </ul>

        </li>
      
    
  </ul>











  
<ul>
  
  <li>
    <a href="https://gitee.com/chaincode" target="_blank" rel="noopener">
        Gitee
      </a>
  </li>
  
</ul>






</nav>




  <script>(function(){var e=document.querySelector("aside .book-menu-content");addEventListener("beforeunload",function(){localStorage.setItem("menu.scrollTop",e.scrollTop)}),e.scrollTop=localStorage.getItem("menu.scrollTop")})()</script>


 
      </div>
    </aside>

    <div class="book-page">
      <header class="book-header">
        
  <div class="flex align-center justify-between">
  <label for="menu-control">
    <img src="/svg/menu.svg" class="book-icon" alt="Menu" />
  </label>

  <strong>go语言底层基础</strong>

  <label for="toc-control">
    
    <img src="/svg/toc.svg" class="book-icon" alt="Table of Contents" />
    
  </label>
</div>


  
  <aside class="hidden clearfix">
    
  
<nav id="TableOfContents">
  <ul>
    <li>
      <ul>
        <li><a href="#go语言相关">Go语言相关</a>
          <ul>
            <li><a href="#gmp模型">GMP模型</a></li>
            <li><a href="#golang-map">Golang Map</a></li>
            <li><a href="#channel">channel</a></li>
            <li><a href="#defer函数">defer函数</a></li>
            <li><a href="#sync包">sync包</a></li>
            <li><a href="#垃圾回收gc">垃圾回收(GC)</a></li>
          </ul>
        </li>
      </ul>
    </li>
  </ul>
</nav>



  </aside>
  
 
      </header>

      
      
  <article class="markdown"><h2 id="go语言相关">
  Go语言相关
  <a class="anchor" href="#go%e8%af%ad%e8%a8%80%e7%9b%b8%e5%85%b3">#</a>
</h2>
<h3 id="gmp模型">
  GMP模型
  <a class="anchor" href="#gmp%e6%a8%a1%e5%9e%8b">#</a>
</h3>
<blockquote>
<p>G    goroutine协程</p>
<p>P     processor处理器</p>
<p>M    thread线程</p>
<p>Processor <font color='red'>它包含了运行goroutine的资源，如果线程想运行goroutine,必须先获取P，P中还包含了可运行的G队列</font>。</p>
</blockquote>
<p>在Go中，<strong>线程是运行goroutine的实体，调度器的功能是把可运行的goroutine分配到工作线程上。</strong></p>
<p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208032219404.jpeg" alt="" /></p>
<ul>
<li><strong>全局队列</strong>：<u>存放等待运行的G</u></li>
<li><strong>P的本地队列</strong>：<font color='red'>同全局队列类似，存放的也是等待运行的G，存的数量有限，<strong>不超过256个。新建 G’时，G’优先加入到 P 的本地队列，如果队列满了，则会把本地队列中一半的 G 移动到全局队列</strong>。</font></li>
<li><strong>P列表</strong>：<font color='red'>所有的P都在程序启动时创建，并保存在数组中，最多有 <code>GOMAXPROCS</code>(可配置) 个</font>。</li>
<li><strong>M</strong>：线程想运行任务就得获取P，从P的本地队列获取G，P队列为空时，M 也会尝试从全局队列<strong>拿</strong>一批 G 放到 P 的本地队列，或从其他 P 的本地队列<strong>偷</strong>一半放到自己 P 的本地队列。M 运行 G，G 执行之后，M 会从 P 获取下一个 G，不断重复下去。</li>
</ul>
<p><strong>Goroutine 调度器和 OS 调度器是通过 M 结合起来的，每个 M 都代表了 1 个内核线程，OS 调度器负责把内核线程分配到 CPU 的核上执行</strong>。</p>
<h5 id="p和m的个数问题">
  P和M的个数问题
  <a class="anchor" href="#p%e5%92%8cm%e7%9a%84%e4%b8%aa%e6%95%b0%e9%97%ae%e9%a2%98">#</a>
</h5>
<p>1、P的数量：</p>
<ul>
<li>由启动时环境变量 $GOMAXPROCS 或者是由 runtime 的方法 GOMAXPROCS() 决定。这意味着在程序执行的任意时刻都只有 $GOMAXPROCS 个 goroutine 在同时运行。</li>
</ul>
<p>2、M的数量：</p>
<ul>
<li>go 语言本身的限制：go 程序启动时，会设置 M 的最大数量，默认 <font color='red'>10000. 但是内核很难支持这么多的线程数，所以这个限制可以忽略。</font></li>
<li>runtime/debug 中的 SetMaxThreads 函数，设置 M 的最大数量</li>
<li>一个 M 阻塞了，会创建新的 M。</li>
</ul>
<p><strong>M与P的数量没有绝对关系，一个M阻塞，P就会去创建或者切换另一个M，所以即使P的数量是1，也有可能会创建很多个M出来。</strong></p>
<h5 id="p和m何时会被创建">
  P和M何时会被创建
  <a class="anchor" href="#p%e5%92%8cm%e4%bd%95%e6%97%b6%e4%bc%9a%e8%a2%ab%e5%88%9b%e5%bb%ba">#</a>
</h5>
<p>1、P 何时创建：<font color='red'>在确定了 P 的最大数量 n 后，运行时系统会根据这个数量创建 n 个 P</font>。</p>
<p>2、M 何时创建：<u>没有足够的 M 来关联 P 并运行其中的可运行的 G</u>。比如所有的 M 此时都阻塞住了，而 P 中还有很多就绪任务，就会去寻找空闲的 M，而没有空闲的，就会去创建新的 M。</p>
<h4 id="调度器的设计策略">
  调度器的设计策略
  <a class="anchor" href="#%e8%b0%83%e5%ba%a6%e5%99%a8%e7%9a%84%e8%ae%be%e8%ae%a1%e7%ad%96%e7%95%a5">#</a>
</h4>
<p><strong>复用线程</strong>：避免频繁的创建、销毁线程，而是对线程的复用。</p>
<p>1）work stealing 机制</p>
<p>当本线程无可运行的 G 时，尝试从其他线程绑定的 P 偷取 G，而不是销毁线程。</p>
<p>2）hand off 机制</p>
<p>当本线程因为 G 进行系统调用阻塞时，线程释放绑定的 P，把 P 转移给其他空闲的线程执行。</p>
<p>利用并行：GOMAXPROCS 设置 P 的数量，最多有 GOMAXPROCS 个线程分布在多个 CPU 上同时运行。GOMAXPROCS 也限制了并发的程度，比如 GOMAXPROCS = 核数/2，则最多利用了一半的 CPU 核进行并行。</p>
<p>抢占：<strong>在 coroutine 中要等待一个协程主动让出 CPU 才执行下一个协程，在 Go 中，一个 goroutine 最多占用 CPU 10ms，防止其他 goroutine 被饿死，这就是 goroutine 不同于 coroutine 的一个地方。</strong></p>
<p>全局 G 队列：<strong>在新的调度器中依然有全局 G 队列，但功能已经被弱化了，当 M 执行 work stealing 从其他 P 偷不到 G 时，它可以从全局 G 队列获取 G。</strong></p>
<h4 id="go-func--调度流程">
  go func () 调度流程
  <a class="anchor" href="#go-func--%e8%b0%83%e5%ba%a6%e6%b5%81%e7%a8%8b">#</a>
</h4>
<p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208032220737.jpeg" alt="" /></p>
<p>从上图我们可以分析出几个结论：</p>
<p>1、我们通过 go func () 来创建一个 goroutine；</p>
<p>2、有两个存储 G 的队列，一个是局部调度器 P 的本地队列、一个是全局 G 队列。新创建的 G 会先保存在 P 的本地队列中，如果 P 的本地队列已经满了就会保存在全局的队列中；</p>
<p>3、G 只能运行在 M 中，一个 M 必须持有一个 P，M 与 P 是 1：1 的关系。M 会从 P 的本地队列弹出一个可执行状态的 G 来执行，如果 P 的本地队列为空，就会想其他的 MP 组合偷取一个可执行的 G 来执行；</p>
<p>4、一个 M 调度 G 执行的过程是一个循环机制；</p>
<p>5、当 M 执行某一个 G 时候如果发生了 syscall 或则其余阻塞操作，M 会阻塞，如果当前有一些 G 在执行，runtime 会把这个线程 M 从 P 中摘除 (detach)，然后再创建一个新的操作系统的线程 (如果有空闲的线程可用就复用空闲线程) 来服务于这个 P；</p>
<p>6、当 M 系统调用结束时候，这个 G 会尝试获取一个空闲的 P 执行，并放入到这个 P 的本地队列。如果获取不到 P，那么这个线程 M 变成休眠状态， 加入到空闲线程中，然后这个 G 会被放入全局队列中。</p>
<h3 id="golang-map">
  Golang Map
  <a class="anchor" href="#golang-map">#</a>
</h3>
<h4 id="底层原理">
  底层原理
  <a class="anchor" href="#%e5%ba%95%e5%b1%82%e5%8e%9f%e7%90%86">#</a>
</h4>
<p>这种类型最大的特点就是查找速度非常快，因为它的底层存储是基于哈希表存储的。</p>
<p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208051558636.png" alt="" /></p>
<p>Go中的map是一个指针，占用8个字节，指向hmap结构体。</p>
<p>hmap包含若干结构为bmap的数组，每个bmap底层都采用链表结构，bmap通常叫bucket。</p>
<p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208051617512.png" alt="" /></p>
<p><strong>hmap结构体</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">hmap</span> <span style="color:#66d9ef">struct</span> {<span style="color:#75715e">// A header for a Go map.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">count</span>     <span style="color:#66d9ef">int</span>  <span style="color:#75715e">// 代表哈希表中的元素个数，调用len(map)时，返回的就是该字段值。
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">flags</span>     <span style="color:#66d9ef">uint8</span>  <span style="color:#75715e">// 状态标志（是否处于正在写入的状态等）
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">B</span>         <span style="color:#66d9ef">uint8</span>  <span style="color:#75715e">//buckets（桶）的对数 如果B=5，则buckets数组的长度 = 2^B=32，意味着有32个桶
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">noverflow</span> <span style="color:#66d9ef">uint16</span> <span style="color:#75715e">// 溢出桶的数量
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">hash0</span>     <span style="color:#66d9ef">uint32</span> <span style="color:#75715e">// 生成hash的随机数种子
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">buckets</span>    <span style="color:#a6e22e">unsafe</span>.<span style="color:#a6e22e">Pointer</span> <span style="color:#75715e">// 指向buckets数组的指针，数组大小为2^B，如果元素个数为0，它为nil。
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">oldbuckets</span> <span style="color:#a6e22e">unsafe</span>.<span style="color:#a6e22e">Pointer</span> <span style="color:#75715e">// 如果发生扩容，oldbuckets是指向老的buckets数组的指针，老的buckets数组大小是新的buckets的1/2;非扩容状态下，它为nil。
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">nevacuate</span>  <span style="color:#66d9ef">uintptr</span>        <span style="color:#75715e">// 表示扩容进度，小于此地址的buckets代表已搬迁完成。
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">extra</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">mapextra</span> <span style="color:#75715e">// 存储溢出桶，这个字段是为了优化GC扫描而设计的，下面详细介绍
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span></code></pre></div><p><strong>bmap结构体</strong></p>
<p><code>bmap</code> 就是我们常说的“桶”，一个桶里面会<strong>最多装 8 个 key</strong>，这些 key 之所以会落入同一个桶，是因为它们经过哈希计算后，哈希结果的低8位是相同的，关于key的定位我们在map的查询中详细说明。在桶内，又会根据 key 计算出来的 hash 值的高 8 位来决定 key 到底落入桶内的哪个位置（一个桶内最多有8个位置)。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">bmap</span> <span style="color:#66d9ef">struct</span> { <span style="color:#75715e">// A bucket for a Go map.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">tophash</span> [<span style="color:#a6e22e">bucketCnt</span>]<span style="color:#66d9ef">uint8</span>        
</span></span><span style="display:flex;"><span><span style="color:#75715e">// len为8的数组
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">// 用来快速定位key是否在这个bmap中
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">// 一个桶最多8个槽位，如果key所在的tophash值在tophash中，则代表该key在这个桶中
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span></code></pre></div><p>上面bmap结构是静态结构，在编译过程中<code>runtime.bmap</code>会拓展成以下结构体：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">bmap</span> <span style="color:#66d9ef">struct</span>{
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">tophash</span> [<span style="color:#ae81ff">8</span>]<span style="color:#66d9ef">uint8</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">keys</span> [<span style="color:#ae81ff">8</span>]<span style="color:#a6e22e">keytype</span> <span style="color:#75715e">// keytype 由编译器编译时候确定
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">values</span> [<span style="color:#ae81ff">8</span>]<span style="color:#a6e22e">elemtype</span> <span style="color:#75715e">// elemtype 由编译器编译时候确定
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">overflow</span> <span style="color:#66d9ef">uintptr</span> <span style="color:#75715e">// overflow指向下一个bmap，overflow是uintptr而不是*bmap类型，保证bmap完全不含指针，是为了减少gc，溢出桶存储到extra字段中
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span></code></pre></div><p>tophash就是用于实现快速定位key的位置，在实现过程中会使用key的hash值的高8位作为tophash值，存放在bmap的tophash字段中</p>
<p>tophash字段不仅存储key哈希值的高8位，还会存储一些状态值，用来表明当前桶单元状态，这些状态值都是小于minTopHash的</p>
<p>为了避免key哈希值的高8位值和这些状态值相等，产生混淆情况，所以当key哈希值高8位若小于minTopHash时候，自动将其值加上minTopHash作为该key的tophash。桶单元的状态值如下：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">emptyRest</span>      = <span style="color:#ae81ff">0</span> <span style="color:#75715e">// 表明此桶单元为空，且更高索引的单元也是空
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">emptyOne</span>       = <span style="color:#ae81ff">1</span> <span style="color:#75715e">// 表明此桶单元为空
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">evacuatedX</span>     = <span style="color:#ae81ff">2</span> <span style="color:#75715e">// 用于表示扩容迁移到新桶前半段区间
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">evacuatedY</span>     = <span style="color:#ae81ff">3</span> <span style="color:#75715e">// 用于表示扩容迁移到新桶后半段区间
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">evacuatedEmpty</span> = <span style="color:#ae81ff">4</span> <span style="color:#75715e">// 用于表示此单元已迁移
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">minTopHash</span>     = <span style="color:#ae81ff">5</span> <span style="color:#75715e">// key的tophash值与桶状态值分割线值，小于此值的一定代表着桶单元的状态，大于此值的一定是key对应的tophash值
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">tophash</span>(<span style="color:#a6e22e">hash</span> <span style="color:#66d9ef">uintptr</span>) <span style="color:#66d9ef">uint8</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">top</span> <span style="color:#f92672">:=</span> uint8(<span style="color:#a6e22e">hash</span> <span style="color:#f92672">&gt;&gt;</span> (<span style="color:#a6e22e">goarch</span>.<span style="color:#a6e22e">PtrSize</span><span style="color:#f92672">*</span><span style="color:#ae81ff">8</span> <span style="color:#f92672">-</span> <span style="color:#ae81ff">8</span>))
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">top</span> &lt; <span style="color:#a6e22e">minTopHash</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">top</span> <span style="color:#f92672">+=</span> <span style="color:#a6e22e">minTopHash</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">top</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><strong>mapextra结构体</strong></p>
<p>当map的key和value都不是指针类型时候，bmap将完全不包含指针，那么gc时候就不用扫描bmap。bmap指向溢出桶的字段overflow是uintptr类型，为了防止这些overflow桶被gc掉，所以需要mapextra.overflow将它保存起来。如果bmap的overflow是*bmap类型，那么gc扫描的是一个个拉链表，效率明显不如直接扫描一段内存(hmap.mapextra.overflow)</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">mapextra</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">overflow</span>    <span style="color:#f92672">*</span>[]<span style="color:#f92672">*</span><span style="color:#a6e22e">bmap</span> <span style="color:#75715e">// overflow 包含的是 hmap.buckets 的 overflow 的 buckets
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">oldoverflow</span> <span style="color:#f92672">*</span>[]<span style="color:#f92672">*</span><span style="color:#a6e22e">bma</span> <span style="color:#75715e">// oldoverflow 包含扩容时 hmap.oldbuckets 的 overflow 的 bucket
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">nextOverflow</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">bmap</span>  <span style="color:#75715e">// 指向空闲的 overflow bucket 的指针
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span></code></pre></div><p><strong>总结</strong></p>
<p>bmap（bucket）内存数据结构可视化如下:</p>
<p><font color='red'>注意到 key 和 value 是各自放在一起的，并不是 <code>key/value/key/value/...</code> 这样的形式，当key和value类型不一样的时候，key和value占用字节大小不一样，使用key/value这种形式可能会因为内存对齐导致内存空间浪费，所以Go采用key和value分开存储的设计，更节省内存空间</font></p>
<p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208051626729.png" alt="" /></p>
<h4 id="实现map的并发安全">
  实现Map的并发安全
  <a class="anchor" href="#%e5%ae%9e%e7%8e%b0map%e7%9a%84%e5%b9%b6%e5%8f%91%e5%ae%89%e5%85%a8">#</a>
</h4>
<p>sync.Map，底层实际上用了一个Map缓存</p>
<p><strong>不要以共享内存的方式来通讯，相反，要经过通讯来共享内存</strong></p>
<h4 id="实现map的有序查找">
  实现Map的有序查找
  <a class="anchor" href="#%e5%ae%9e%e7%8e%b0map%e7%9a%84%e6%9c%89%e5%ba%8f%e6%9f%a5%e6%89%be">#</a>
</h4>
<p>利用一个辅助slice</p>
<h4 id="map可以用数组作为key吗">
  Map可以用数组作为Key吗
  <a class="anchor" href="#map%e5%8f%af%e4%bb%a5%e7%94%a8%e6%95%b0%e7%bb%84%e4%bd%9c%e4%b8%bakey%e5%90%97">#</a>
</h4>
<p>Golang中的map的key必须是可以比较的，可以使用==运算符进行比较。</p>
<p>因slice，map，function不可以比较，所以不能作为key</p>
<p>int、string、bool、array数组、channel、指针可以，以及包含前面类型的struct。</p>
<blockquote>
<p>因为切片是引用类型，并且不可比较.</p>
<ul>
<li>引用类型，比较地址没有意义。</li>
<li>切片有len，cap，比较的维度不好衡量，因此go设计的时候就不允许切片可以比较。</li>
</ul>
<p>由于map中的value可以是slice，这就使得包含slice的结构体包括函数，结构体等也是不可比较的。<strong>这里的不可比较结构体是包含slice的结构体！</strong></p>
</blockquote>
<p><strong>因此map是不可比较的，自然不能作为map的key，而value是任意类型</strong>。</p>
<h3 id="channel">
  channel
  <a class="anchor" href="#channel">#</a>
</h3>
<h4 id="底层原理-1">
  底层原理
  <a class="anchor" href="#%e5%ba%95%e5%b1%82%e5%8e%9f%e7%90%86-1">#</a>
</h4>
<p><strong>背景</strong></p>
<ul>
<li>Go语言提供了一种不同的并发模型-通信顺序进程（communicating sequential processes,CSP)</li>
<li>设计模式：通过通信的方式共享内存</li>
<li>channel收发操作遵循先进先出(FIFO)的设计</li>
</ul>
<p><strong>底层结构:</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">hchan</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">qcount</span>   <span style="color:#66d9ef">uint</span>           <span style="color:#75715e">// channel中的元素个数
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">dataqsiz</span> <span style="color:#66d9ef">uint</span>           <span style="color:#75715e">// channel中循环队列的长度
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">buf</span>      <span style="color:#a6e22e">unsafe</span>.<span style="color:#a6e22e">Pointer</span> <span style="color:#75715e">// channel缓冲区数据指针
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">elemsize</span> <span style="color:#66d9ef">uint16</span>            <span style="color:#75715e">// buffer中每个元素的大小
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">closed</span>   <span style="color:#66d9ef">uint32</span>            <span style="color:#75715e">// channel是否已经关闭，0未关闭
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">elemtype</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">_type</span> <span style="color:#75715e">// channel中的元素的类型
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">sendx</span>    <span style="color:#66d9ef">uint</span>   <span style="color:#75715e">// channel发送操作处理到的位置
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">recvx</span>    <span style="color:#66d9ef">uint</span>   <span style="color:#75715e">// channel接收操作处理到的位置
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">recvq</span>    <span style="color:#a6e22e">waitq</span>  <span style="color:#75715e">// 等待接收的sudog（sudog为封装了goroutine和数据的结构）队列由于缓冲区空间不足而阻塞的Goroutine列表
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">sendq</span>    <span style="color:#a6e22e">waitq</span>  <span style="color:#75715e">// 等待发送的sudogo队列，由于缓冲区空间不足而阻塞的Goroutine列表
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">lock</span> <span style="color:#a6e22e">mutex</span>   <span style="color:#75715e">// 一个轻量级锁
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span></code></pre></div><p>我们通过make创建一个缓冲区大小为5，元素类型为int的channel。ch是存在于函数栈帧上的一个指针，指向堆上的hchan数据结构。</p>
<p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208071031840.png" alt="" /></p>
<p>因为channel免不了支持协程间并发访问，所以要有一个锁（lock）来保护整个channel数据结构。</p>
<p>对于有缓冲区channel来讲，需要知道缓冲区在哪里（buf），已经存储量多少个元素（qcount），最多存储多少个元素（dataqsize），每个元素占多大空间（elemsize)，所以实际上缓冲区就是一个数组。因为Golang运行时中，内存复制，垃圾回收等机制，依赖数据的类型信息，所以hchan这里还要有一个指针，指向元素类型的类型元数据。此外，channel支持交替的读(接收)，写(发送)。需要分别记录读，写 下标的位置，当读和写不能立即完成时，需要能够让当前协程在channel上等待，待到条件满足时，要能够立即唤醒等待的协程，所以要有两个等待队列，分别针对读和写。此外，channel能够close，所以还要记录它的关闭状态，综上所述，channel底层就长这样。</p>
<p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208071038344.png" alt="" /></p>
<p><strong>channel创建</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">ch</span> <span style="color:#f92672">:=</span> make(<span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">int</span>,<span style="color:#ae81ff">3</span>)
</span></span></code></pre></div><ul>
<li>创建channel实际上就是在内存中实例化了一个hchan结构体，并返回一个chan指针</li>
<li>channel在函数间传递都是使用的这个指针，这就是为什么函数传递中无需使用channel的指针，直接使用channel就可以了，因为channel本身就是一个指针</li>
</ul>
<p><strong>channel发送数据：</strong></p>
<pre tabindex="0"><code>ch &lt;- 1
ch &lt;- 2
</code></pre><ul>
<li>检查sendq是否为空，如果不为空，且没有缓冲区，则从sendq头部取一个goroutine，将数据读取出来，并唤醒对应的goroutine，结束读取过程。</li>
<li>如果sendq不为空，且有缓冲区，则说明缓冲区已满，则从缓冲区中首部读取数据，把sendq头部的goroutine数据写入缓冲区尾部，并将goroutine唤醒，结束读取过程。</li>
<li>如果sendq为空，缓冲区有数据，则直接从缓冲区读取数据，结束读取过程。</li>
<li>如果sendq为空，且缓冲区没有数据，则只能将当前的goroutine加入到recvq，并进入waiting状态，等待被写goroutine唤醒。</li>
</ul>
<p><strong>channel规则：</strong></p>
<table>
<thead>
<tr>
<th>操作</th>
<th>空channel</th>
<th>已关闭channel</th>
<th>活跃中的channel</th>
</tr>
</thead>
<tbody>
<tr>
<td>close(ch)</td>
<td>panic</td>
<td>panic</td>
<td>成功关闭</td>
</tr>
<tr>
<td>ch&lt;- v</td>
<td>永远阻塞</td>
<td>panic</td>
<td>成功发送或阻塞</td>
</tr>
<tr>
<td>v,ok = &lt;-ch</td>
<td>永远阻塞</td>
<td>不阻塞</td>
<td>成功接收或阻塞</td>
</tr>
</tbody>
</table>
<h4 id="channel阻塞非阻塞操作多路select">
  channel阻塞、非阻塞操作、多路select
  <a class="anchor" href="#channel%e9%98%bb%e5%a1%9e%e9%9d%9e%e9%98%bb%e5%a1%9e%e6%93%8d%e4%bd%9c%e5%a4%9a%e8%b7%afselect">#</a>
</h4>
<p>接下来，我们继续使用ch，初始状态下，ch的缓冲区为空，读、写下标都指向下标0的位置，等待队列也都为空。</p>
<p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208071042820.png" alt="" /></p>
<p>然后一个协程g1向ch中发送数据，因为没有协程在等待接收数据，所以元素都被存到缓冲区中，sendx从0开始向后挪，</p>
<p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208071049144.png" alt="" /></p>
<p>第5个元素会放到下标为4的位置，然后sendx重新回到0，此时缓冲区已经没有空闲位置了。</p>
<p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208071049098.png" alt="" /></p>
<p>所以接下来发送的第6个元素无处可放，g1会进到ch的发送等待队列中。这是一个sudog类型的链表，里面会记录哪个协程在等待，等待哪个channel，等待发送的数据在哪里，等等消息。</p>
<p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208071052730.png" alt="" /></p>
<p>接下来协程g2从ch接收一个元素，recv指向下个位置，第0个位置就空出来了，</p>
<p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208071053477.png" alt="" /></p>
<p>所以会唤醒sendq中的g1，将elem指向的数据发送给ch，然后缓冲区再次满了，sendq队列为空。</p>
<p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208071055409.png" alt="" /></p>
<p>在这一过程中，可以看到sendx和recvx，都会从0到4再到0，所以channel的缓冲区，被称为&quot;环形&quot;缓冲区。</p>
<p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208071056770.png" alt="" /></p>
<p>如果像这样给channel发送数据，只有在缓冲区还有空闲位置，或者有协程在等着接收数据的时候，才不会发送阻塞。</p>
<p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208071057477.png" alt="" /></p>
<p>碰到ch为nil，或者ch没有缓冲区，而且也没有协程等着接收数据，又或者，ch有缓冲区但缓冲区已用尽的情况，都会发送阻塞</p>
<p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208071057640.png" alt="" /></p>
<p><strong>解决发送阻塞</strong></p>
<p>那如果不想阻塞的话，就可以使用select，使用select这种写法时，如果检测到ch可以发送数据，就会执行<u>case</u>分支；如果会阻塞，就会执行<u>default</u>分支了。</p>
<p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208071059740.png" alt="" /></p>
<p><strong>接收阻塞</strong></p>
<p>这是发送数据的写法，接收数据的写法要更多一点。第一种写法会将结果丢弃，第二种写法将结果赋给变量v，第三种是comma ok风格的写法，ok为false时表示ch已关闭，此时v是channel元素类型的零值。这几种写法都允许发生阻塞，只有在缓冲区种有数据，或者有协程等着发送数据时 ，才不会阻塞。如果ch为nil，或者ch无缓冲而且没有协程等着发送数据，又或者ch有缓冲但缓冲区无数据时，都会发生阻塞。</p>
<p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208071102928.png" alt="" /></p>
<p><strong>解决接收阻塞</strong></p>
<p>如果无论如何都不想阻塞，同样可以采用非阻塞式写法，这样在检测到ch的recv操作不会阻塞时，就会执行case分支，如果会阻塞，就会执行default分支。</p>
<p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208071103053.png" alt="" /></p>
<p><strong>多路select</strong></p>
<p>上面的selec只是针对的单个channel的操作；
多路select指的是存在两个或者更多的case分支，每个分支可以是一个channel的send或recv操作。例如一个协程通过多路select等待ch1和ch2。这里的default分支是可选的。</p>
<p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208071105892.png" alt="" /></p>
<p>我们暂且把这个协程记为g1，多路select会被编译器转换为runtime.selectgo函数调用。
第一个参数cas0指向一个数组，数组里装的是select中所有的case分支，顺序是send在前，recv在后。
第二个参数order0指向一个uint16类型的数组，数组大小等于case分支的两倍。实际上被用作两个数组，第一个数组用来对所有channel的轮询进行乱序，第二个数组用来对所有channel的加锁操作进行排序。轮询需要乱序才能保障公平性，而按照固定算法确定加锁顺序才能避免死锁。</p>
<p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208071106464.png" alt="" /></p>
<p>第三个参数pc0和race检测相关，我们暂时不关心。
第四、五个参数nsends和nrecvs分别表示所有case中执行send和recv操作的分支分别有多少个。
第六个参数block表示多路select是否要阻塞等待，对应到代码中，就是有default分支的不会阻塞，没有的会阻塞。</p>
<p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208071107550.png" alt="" /></p>
<p>再来看第一个返回值，它代表最终哪个case分支被执行了，对应到参数cas0数组的下标。但是如果进到default分支则对应-1。第二个返回值用于在执行recv操作的case分支时，表明是实际接收到了一个值，还是因channel关闭而得到了零值。</p>
<p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208071108149.png" alt="" /></p>
<p>多路select需要进行轮询来确定哪个case分支可操作了，但是轮询前要先加锁，所以selectgo函数执行时，会先按照有序的加锁顺序，对所有channel加锁，然后按照乱序的轮询顺序检查所有channel的等待队列和缓冲区。</p>
<p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208071108869.png" alt="" /></p>
<p>假如检查到ch1时，发现有数据可读，那就直接拷贝数据，进入对应分支。</p>
<p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208071109503.png" alt="" /></p>
<p>假如所有channel都不可操作，就把当前协程添加到所有channel的sendq或recvq中。对应到本例中，g1会被添加到ch1的recvq，以及ch2的sendq中。之后g1会挂起，并解锁所有的channel的锁。</p>
<p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208071109589.png" alt="" /></p>
<p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208071110092.png" alt="" /></p>
<p>假如接下来ch1有数据可读了，g1就会被唤醒，完成对应分支的操作。</p>
<p>完成对应分支的操作后，会再次按照加锁顺序对所有channel加锁，然后从所有sendq或recvq中将自己移除，最后全部解锁，然后返回。</p>
<p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208071111252.png" alt="" /></p>
<p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208071111771.png" alt="" /></p>
<h4 id="golang协程间如何通信">
  Golang协程间如何通信
  <a class="anchor" href="#golang%e5%8d%8f%e7%a8%8b%e9%97%b4%e5%a6%82%e4%bd%95%e9%80%9a%e4%bf%a1">#</a>
</h4>
<p>GO协程通过通道来通信而协程通过让出和恢复操作来通信。</p>
<p>goroutine是一个与其他goroutine并发运行在同一地址空间的Go函数或方法。一个运行的程序由一个或更多个goroutine组成。它与线程、协程、进程等不同。它是一个goroutine。</p>
<h3 id="defer函数">
  defer函数
  <a class="anchor" href="#defer%e5%87%bd%e6%95%b0">#</a>
</h3>
<h4 id="原理">
  原理
  <a class="anchor" href="#%e5%8e%9f%e7%90%86">#</a>
</h4>
<p><strong>defer数据结构：</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">_defer</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">sp</span>      <span style="color:#66d9ef">uintptr</span>   <span style="color:#75715e">//函数栈指针
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">pc</span>      <span style="color:#66d9ef">uintptr</span>   <span style="color:#75715e">//程序计数器
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">fn</span>      <span style="color:#f92672">*</span><span style="color:#a6e22e">funcval</span>  <span style="color:#75715e">//函数地址
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">link</span>    <span style="color:#f92672">*</span><span style="color:#a6e22e">_defer</span>   <span style="color:#75715e">//指向自身结构的指针，用于链接多个defer
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span></code></pre></div><p>defer后面一定要接一个函数，所以defer的数据结构跟一般函数类似，也有栈指针、程序计数器、函数地址等等。与函数不同的是它含有一个指针，可用于指向另一个defer，每个goroutine数据结构中实际上也有一个defer指针，该指针指向一个defer的<strong>链表</strong>，每次声明一个defer时就将defer插入到单链表表头，每次执行defer就从单链表表头取出一个defer执行。</p>
<p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208061528645.png" alt="" /></p>
<p>如图所示，新声明的defer（B()）总是添加到链表头部，函数返回前执行defer则是从链表首部依次取出执行，形成一个栈结构。</p>
<p><strong>defer的功能</strong></p>
<p>defer用来声明一个延迟函数，可以定义多个延时函数，这些函数会放到一个栈中，当函数执行到最后时，这些defer语句会按照逆序执行，最后该函数返回。</p>
<p>通常用defer来做一些资源释放，比如关闭io操作。</p>
<p><strong>1、defer 的执行顺序</strong></p>
<p>一个函数中使用多个defer时，它们是一个 “栈” 的关系，也就是先进后出，先在后面的defer先执行。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">func1</span>()
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">func2</span>()
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">func3</span>()
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">func1</span>() {
</span></span><span style="display:flex;"><span>   <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Print</span>(<span style="color:#e6db74">&#34;A&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">func2</span>() {
</span></span><span style="display:flex;"><span>   <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Print</span>(<span style="color:#e6db74">&#34;B&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">func3</span>() {
</span></span><span style="display:flex;"><span>   <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Print</span>(<span style="color:#e6db74">&#34;C&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><img src="https://blockchaincode.oss-cn-hangzhou.aliyuncs.com/Hexo_img/202208061630605.png" alt="" /></p>
<p>执行输出为：C B A</p>
<p><strong>2、defer 与 return 谁先谁后</strong></p>
<p>根据代码运行情况可以理解为：return 之后的语句先执行，defer 后的语句后执行。<font color='red'><u>不过，defer执行时是可以改变return中的返回值的。</u></font></p>
<p><strong>3、当defer被声明时，其参数就会被实时解析</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">a</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">i</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">i</span><span style="color:#f92672">++</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>运行结果是0
这是因为defer后面定义的是一个带变量的函数: fmt.Println(i). 但这个变量(i)在defer被声明的时候，就已经确定值了，这里的变量为整型为值传递，个人理解是为defer后的函数拷贝了一个i变量且=0。</p>
<p>但若defer后的函数不带变量呢：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">a</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">defer</span> <span style="color:#66d9ef">func</span>() {<span style="color:#75715e">//defer1
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>		<span style="color:#a6e22e">i</span><span style="color:#f92672">++</span><span style="color:#75715e">//2+1
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>		<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;a defer1:&#34;</span>, <span style="color:#a6e22e">i</span>)<span style="color:#75715e">//i=3
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>	}()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">defer</span> <span style="color:#66d9ef">func</span>() {<span style="color:#75715e">//defer2
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>		<span style="color:#a6e22e">i</span><span style="color:#f92672">++</span><span style="color:#75715e">//1+1
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>		<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;a defer2:&#34;</span>, <span style="color:#a6e22e">i</span>)<span style="color:#75715e">//i=2
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>	}()
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">i</span><span style="color:#f92672">++</span><span style="color:#75715e">//i=1
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">a</span>()
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>运行结果：</p>
<p>a defer2: 2</p>
<p>a defer1: 3</p>
<p>无变量传入，即使defer的函数内部有外部定义的变量也不会在defer声明的时候确定值，将在外部函数执行完返回的时候依次执行相应操作（i++）。</p>
<p><strong>4、有名函数返回值遇见 defer 情况</strong></p>
<p>先 return，再 defer，所以在执行完 return 之后，还要再执行 defer 里的语句，依然可以修改本应该返回的结果。</p>
<p><strong>a.已定义返回值：</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">DeferFunc1</span>(<span style="color:#a6e22e">i</span> <span style="color:#66d9ef">int</span>) (<span style="color:#a6e22e">t</span> <span style="color:#66d9ef">int</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">t</span> = <span style="color:#a6e22e">i</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">defer</span> <span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">t</span> <span style="color:#f92672">+=</span> <span style="color:#ae81ff">3</span>
</span></span><span style="display:flex;"><span>	}()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">t</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>()  {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">DeferFunc1</span>(<span style="color:#ae81ff">1</span>))
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>运行结果：4</p>
<ul>
<li>
<p>将返回值 t 赋值为传入的 i，此时 t 为 1</p>
</li>
<li>
<p>执行 return 语句将 t 赋值给 t（等于啥也没做）</p>
</li>
<li>
<p>执行 defer 方法，将 t + 3 = 4</p>
</li>
<li>
<p>函数返回 4</p>
<p>因为 t 的作用域为整个函数所以修改有效。</p>
</li>
</ul>
<p><strong>b.未定义返回值：</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">DeferFunc2</span>(<span style="color:#a6e22e">i</span> <span style="color:#66d9ef">int</span>) <span style="color:#66d9ef">int</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">t</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">i</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">defer</span> <span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">t</span> <span style="color:#f92672">+=</span> <span style="color:#ae81ff">3</span>
</span></span><span style="display:flex;"><span>	}()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">t</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>()  {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">DeferFunc2</span>(<span style="color:#ae81ff">1</span>))
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>运行结果：1</p>
<ul>
<li>创建变量 t 并赋值为 1</li>
<li>执行 return 语句，注意这里是将 t 赋值给返回值，此时返回值为 1（这个返回值并不是 t）</li>
<li>执行 defer 方法，将 t + 3 = 4</li>
<li>函数返回返回值 1</li>
</ul>
<p><strong>5、defer 遇见 panic</strong>
<strong>a.第一种情况：遇到panic不捕获</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;defer1&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;defer2&#34;</span>)
</span></span><span style="display:flex;"><span>	panic(<span style="color:#e6db74">&#34;发生异常&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;defer3&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>运行结果：</p>
<p>defer2
defer1
panic: 发生异常</p>
<p>panic后的defer不会入栈（后面的代码运行不到）。</p>
<p><strong>b.第二种情况：defer 遇见 panic，并捕获异常</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">defer_call</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">defer</span> <span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;defer: panic 之前1, 捕获异常&#34;</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> recover(); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	}()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">defer</span> <span style="color:#66d9ef">func</span>() { <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;defer: panic 之前2, 不捕获&#34;</span>) }()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	panic(<span style="color:#e6db74">&#34;异常内容&#34;</span>)  <span style="color:#75715e">//触发defer出栈
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">defer</span> <span style="color:#66d9ef">func</span>() { <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;defer: panic 之后, 永远执行不到&#34;</span>) }()
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">defer_call</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;main 正常结束&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>运行结果：</p>
<p>defer: panic 之前2, 不捕获
defer: panic 之前1, 捕获异常
异常内容
main 正常结束</p>
<p>defer 最大的功能是 panic 后依然有效，main函数正常运行，所以 defer 可以保证你的一些资源一定会被关闭，从而避免一些异常出现的问题。</p>
<p><strong>c.第三种情况：defer中含panic</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>()  {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">defer</span> <span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> recover(); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span>{
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>		}<span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;fatal&#34;</span>)
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	}()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">defer</span> <span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span>		panic(<span style="color:#e6db74">&#34;defer panic1&#34;</span>)
</span></span><span style="display:flex;"><span>	}()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">defer</span> <span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span>		panic(<span style="color:#e6db74">&#34;defer panic2&#34;</span>)
</span></span><span style="display:flex;"><span>	}()
</span></span><span style="display:flex;"><span>	panic(<span style="color:#e6db74">&#34;panic&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>运行结果：defer panic1</p>
<p>触发 panic(“panic”) 后 defer 顺序出栈执行，第一个被执行的 defer 中有 panic(“defer panic”) 异常语句，这个异常将会覆盖掉 main 中的异常 panic(“panic”)，“defer panic1&quot;又会覆盖掉&quot;defer panic2”，最后这个异常被栈底的defer捕获到。</p>
<p><strong>6、 defer 下的函数参数包含子函数</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">function</span>(<span style="color:#a6e22e">index</span> <span style="color:#66d9ef">int</span>, <span style="color:#a6e22e">value</span> <span style="color:#66d9ef">int</span>) <span style="color:#66d9ef">int</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Print</span>(<span style="color:#a6e22e">index</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">index</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">function</span>(<span style="color:#ae81ff">1</span>, <span style="color:#a6e22e">function</span>(<span style="color:#ae81ff">3</span>, <span style="color:#ae81ff">0</span>))
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">function</span>(<span style="color:#ae81ff">2</span>, <span style="color:#a6e22e">function</span>(<span style="color:#ae81ff">4</span>, <span style="color:#ae81ff">0</span>))
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>运行结果：3 4 2 1</p>
<p>这里，有 4 个函数，他们的 index 序号分别为 1，2，3，4。那么这 4 个函数的先后执行顺序是什么呢？这里面有两个 defer， 所以 defer 一共会压栈两次，先进栈 1，后进栈 2。 那么在压栈 function1 的时候，需要连同函数地址、函数形参一同进栈，那么为了得到 function1 的第二个参数的结果，所以就需要先执行 function3 将第二个参数算出，那么 function3 就被第一个执行。同理压栈 function2，就需要执行 function4 算出 function2 第二个参数的值。然后函数结束，先出栈 fuction2、再出栈 function1.</p>
<h4 id="defer函数的使用场景">
  defer函数的使用场景
  <a class="anchor" href="#defer%e5%87%bd%e6%95%b0%e7%9a%84%e4%bd%bf%e7%94%a8%e5%9c%ba%e6%99%af">#</a>
</h4>
<p>延迟Close、recover panic</p>
<h3 id="sync包">
  sync包
  <a class="anchor" href="#sync%e5%8c%85">#</a>
</h3>
<h4 id="临界区">
  临界区
  <a class="anchor" href="#%e4%b8%b4%e7%95%8c%e5%8c%ba">#</a>
</h4>
<p>有时候在Go代码中可能会存在多个goroutine同时操作一个资源区（临界区），这种情况会发生竟态问题（数据竟态）。</p>
<p>临界区：当程序并发地运行时，多个 [Go 协程]不应该同时访问那些修改共享资源的代码。这些修改共享资源的代码称为临界区。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;sync&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">x</span> = <span style="color:#ae81ff">10</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">wg</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">WaitGroup</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">add</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#ae81ff">5000</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">x</span> = <span style="color:#a6e22e">x</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Done</span>()
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Add</span>(<span style="color:#ae81ff">2</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">go</span> <span style="color:#a6e22e">add</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">go</span> <span style="color:#a6e22e">add</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Wait</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">x</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>代码中我们开启了两个goroutine去累加变量x的值，这两个goroutine在访问和修改x变量的时候就会存在数据竞争，导致最后的结果与期待的不符。</p>
<h4 id="互斥锁">
  互斥锁
  <a class="anchor" href="#%e4%ba%92%e6%96%a5%e9%94%81">#</a>
</h4>
<p>Mutex 用于提供一种加锁机制（Locking Mechanism），可确保在某时刻只有一个协程在临界区运行，以防止出现竞态条件。</p>
<p>Mutex 可以在 [sync] 包内找到。[Mutex] 定义了两个方法：[Lock]和 [Unlock](。所有在 <code>Lock</code> 和 <code>Unlock</code> 之间的代码，都只能由一个 Go 协程执行，于是就可以避免竞态条件。</p>
<pre tabindex="0"><code>mutex.Lock()  
x = x + 1  
mutex.Unlock()
</code></pre><p>如果有一个 Go 协程已经持有了锁（Lock），当其他协程试图获得该锁时，这些协程会被阻塞，直到 Mutex 解除锁定为止</p>
<p>互斥锁是一种常用的控制共享资源访问的方法，它能够保证同时只有一个goroutine可以访问共享资源。Go语言中使用sync包的Mutex类型来实现互斥锁。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;sync&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">x</span> = <span style="color:#ae81ff">10</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">wg</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">WaitGroup</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">lock</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">Mutex</span>  <span style="color:#75715e">// 值类型，不需要初始化
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">add</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#ae81ff">5000</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">lock</span>.<span style="color:#a6e22e">Lock</span>()
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">x</span> = <span style="color:#a6e22e">x</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">lock</span>.<span style="color:#a6e22e">Unlock</span>()
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Done</span>()
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Add</span>(<span style="color:#ae81ff">2</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">go</span> <span style="color:#a6e22e">add</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">go</span> <span style="color:#a6e22e">add</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Wait</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">x</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h4 id="读写锁">
  读写锁
  <a class="anchor" href="#%e8%af%bb%e5%86%99%e9%94%81">#</a>
</h4>
<p>互斥锁是完全互斥的，但是有很多实际的场景下是读多写少的，当我们并发的去读取一个资源不涉及资源修改的时候是没有必要加锁的，这种场景下使用读写锁是更好的一种选择。读写锁在Go语言中使用sync包中的RWMutex类型。</p>
<p>读写锁分为两种：读锁和写锁。当一个goroutine获取读锁之后，其他的goroutine如果是获取读锁会继续获得锁，如果是获取写锁就会等待；当一个goroutine获取写锁之后，其他的goroutine无论是获取读锁还是写锁都会等待。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;sync&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;time&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">x</span> = <span style="color:#ae81ff">10</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">wg</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">WaitGroup</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">//var rwlock sync.RWMutex  // 值类型，不需要初始化
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">//var lock sync.Mutex  // 值类型，不需要初始化
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">lock</span>   <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">Mutex</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">rwlock</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">RWMutex</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">write</span>()  {
</span></span><span style="display:flex;"><span>	<span style="color:#75715e">//rwlock.Lock() // 写锁都用Lock
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>	<span style="color:#a6e22e">lock</span>.<span style="color:#a6e22e">Lock</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Sleep</span>(<span style="color:#ae81ff">1</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Millisecond</span>) <span style="color:#75715e">// 模拟写耗时1毫秒
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>	<span style="color:#a6e22e">x</span>=<span style="color:#a6e22e">x</span><span style="color:#f92672">+</span><span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>	<span style="color:#75715e">//rwlock.Unlock()
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>	<span style="color:#a6e22e">lock</span>.<span style="color:#a6e22e">Unlock</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Done</span>()
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">read</span>()  {
</span></span><span style="display:flex;"><span>	<span style="color:#75715e">//rwlock.RLock() // 读锁用RLock
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>	<span style="color:#a6e22e">lock</span>.<span style="color:#a6e22e">Lock</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Sleep</span>(<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Millisecond</span>) <span style="color:#75715e">// 模拟读耗时1毫秒
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>	<span style="color:#75715e">//fmt.Printf(&#34;x 现在的值是：%d\n&#34;,x)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>	<span style="color:#75715e">//rwlock.RUnlock()
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>	<span style="color:#a6e22e">lock</span>.<span style="color:#a6e22e">Unlock</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Done</span>()
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#75715e">// 统计开始时间
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>	<span style="color:#a6e22e">time1</span><span style="color:#f92672">:=</span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#75715e">// 开10个协程写
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#ae81ff">10</span> ; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Add</span>(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">go</span> <span style="color:#a6e22e">write</span>()
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#75715e">// 开1000个协程读
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#ae81ff">1000</span> ; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Add</span>(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">go</span> <span style="color:#a6e22e">read</span>()
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Wait</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;x最终值为：&#34;</span>,<span style="color:#a6e22e">x</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#75715e">// 统计结束时间
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>	<span style="color:#a6e22e">time2</span><span style="color:#f92672">:=</span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">time2</span>.<span style="color:#a6e22e">Sub</span>(<span style="color:#a6e22e">time1</span>))  <span style="color:#75715e">// 结束时间-开始时间
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>	<span style="color:#75715e">// 使用读写锁:15.387426ms
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>	<span style="color:#75715e">// 使用互斥锁：1.26868106s
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">需要注意的是读写锁非常适合读多写少的场景</span><span style="color:#960050;background-color:#1e0010">，</span><span style="color:#a6e22e">如果读和写的操作差别不大</span><span style="color:#960050;background-color:#1e0010">，</span><span style="color:#a6e22e">读写锁的优势就发挥不出来</span>
</span></span></code></pre></div><h4 id="syncwaitgroup">
  sync.WaitGroup
  <a class="anchor" href="#syncwaitgroup">#</a>
</h4>
<p>在代码中生硬的使用time.Sleep肯定是不合适的，Go语言中可以使用sync.WaitGroup来实现并发任务的同步。 sync.WaitGroup有以下几个方法：</p>
<table>
<thead>
<tr>
<th>方法名</th>
<th>功能</th>
</tr>
</thead>
<tbody>
<tr>
<td>(wg * WaitGroup) Add(delta int)</td>
<td>计数器+delta</td>
</tr>
<tr>
<td>(wg *WaitGroup) Done()</td>
<td>计数器-1</td>
</tr>
<tr>
<td>(wg *WaitGroup) Wait()</td>
<td>阻塞直到计数器变为0</td>
</tr>
</tbody>
</table>
<p>sync.WaitGroup内部维护着一个计数器，计数器的值可以增加和减少。例如当我们启动了N 个并发任务时，就将计数器值增加N。每个任务完成时通过调用Done()方法将计数器减1。通过调用Wait()来等待并发任务执行完，当计数器值为0时，表示所有并发任务已经完成。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">wg</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">WaitGroup</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">hello</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Done</span>()
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;Hello World!&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Add</span>(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">go</span> <span style="color:#a6e22e">hello</span>() <span style="color:#75715e">// 启动另外一个goroutine去执行hello函数
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;主协程结束!&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Wait</span>()
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>需要注意sync.WaitGroup是一个结构体，传递的时候要传递指针。</p>
<h4 id="synconce">
  sync.Once
  <a class="anchor" href="#synconce">#</a>
</h4>
<p>在编程的很多场景下我们需要确保某些操作在高并发的场景下只执行一次，例如只加载一次配置文件、只关闭一次通道等。</p>
<p>Go语言中的sync包中提供了一个针对只执行一次场景的解决方案–sync.Once。</p>
<p>sync.Once只有一个Do方法，其签名如下：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">o</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Once</span>) <span style="color:#a6e22e">Do</span>(<span style="color:#a6e22e">f</span> <span style="color:#66d9ef">func</span>()) {}
</span></span></code></pre></div><p>注意：如果要执行的函数f需要传递参数就需要搭配闭包来使用。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;sync&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span><span style="color:#75715e">// 实现单例
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">// 定义一个 sync.Once
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">one</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">Once</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// 定义一个animalSig的指针变量
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">animalSig</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Animal</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// 定义一个结构体
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Animal</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">name</span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">age</span>  <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">getAnimalInstance</span>() <span style="color:#f92672">*</span><span style="color:#a6e22e">Animal</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">one</span>.<span style="color:#a6e22e">Do</span>(<span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;只会执行一次&#34;</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">animalSig</span> = <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">Animal</span>{<span style="color:#e6db74">&#34;狗狗&#34;</span>, <span style="color:#ae81ff">1</span>}
</span></span><span style="display:flex;"><span>	})
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">animalSig</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">wg</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">WaitGroup</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#ae81ff">10</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Add</span>(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">go</span> <span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">res</span><span style="color:#f92672">:=</span><span style="color:#a6e22e">getAnimalInstance</span>()
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;单例animalSig地址为：%p\n&#34;</span>,<span style="color:#a6e22e">res</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Done</span>()
</span></span><span style="display:flex;"><span>		}()
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Wait</span>()
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>sync.Once其实内部包含一个互斥锁和一个布尔值，互斥锁保证布尔值和数据的安全，而布尔值用来记录初始化是否完成。这样设计就能保证初始化操作的时候是并发安全的并且初始化操作也不会被执行多次。</p>
<h4 id="syncmap">
  sync.Map
  <a class="anchor" href="#syncmap">#</a>
</h4>
<p>Go语言中内置的map不是并发安全的。请看下面的示例：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;strconv&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;sync&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span><span style="color:#75715e">// 定义一个map
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">m1</span> = make(<span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#66d9ef">string</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">setMap</span>(<span style="color:#a6e22e">key</span>, <span style="color:#a6e22e">valeu</span> <span style="color:#66d9ef">string</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">m1</span>[<span style="color:#a6e22e">key</span>] = <span style="color:#a6e22e">valeu</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">getMap</span>(<span style="color:#a6e22e">key</span> <span style="color:#66d9ef">string</span>) <span style="color:#66d9ef">string</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">m1</span>[<span style="color:#a6e22e">key</span>]
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">wg</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">WaitGroup</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#ae81ff">10</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Add</span>(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">go</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">n</span> <span style="color:#66d9ef">int</span>) {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">key</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">strconv</span>.<span style="color:#a6e22e">Itoa</span>(<span style="color:#a6e22e">i</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">setMap</span>(<span style="color:#a6e22e">key</span>, <span style="color:#a6e22e">key</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">getMap</span>(<span style="color:#a6e22e">key</span>))
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Done</span>()
</span></span><span style="display:flex;"><span>		}(<span style="color:#a6e22e">i</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Wait</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#75715e">//报错：fatal error: concurrent map writes
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span></code></pre></div><p>上面的代码开启少量几个goroutine的时候可能没什么问题，当并发多了之后执行上面的代码就会报fatal error: concurrent map writes错误。</p>
<p>像这种场景下就需要为 map 加锁来保证并发的安全性了，Go语言的<code>sync</code>包中提供了一个开箱即用的并发安全版 map——<code>sync.Map</code>。开箱即用表示其不用像内置的 map 一样使用 make 函数初始化就能直接使用。同时<code>sync.Map</code>内置了诸如<code>Store</code>、<code>Load</code>、<code>LoadOrStore</code>、<code>Delete</code>、<code>Range</code>等操作方法。</p>
<table>
<thead>
<tr>
<th style="text-align:center">方法名</th>
<th style="text-align:center">功能</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">func (m *Map) Store(key, value interface{})</td>
<td style="text-align:center">存储key-value数据</td>
</tr>
<tr>
<td style="text-align:center">func (m *Map) Load(key interface{}) (value interface{}, ok bool)</td>
<td style="text-align:center">查询key对应的value</td>
</tr>
<tr>
<td style="text-align:center">func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool)</td>
<td style="text-align:center">查询或存储key对应的value</td>
</tr>
<tr>
<td style="text-align:center">func (m *Map) LoadAndDelete(key interface{}) (value interface{}, loaded bool)</td>
<td style="text-align:center">查询并删除key</td>
</tr>
<tr>
<td style="text-align:center">func (m *Map) Delete(key interface{})</td>
<td style="text-align:center">删除key</td>
</tr>
<tr>
<td style="text-align:center">func (m *Map) Range(f func(key, value interface{}) bool)</td>
<td style="text-align:center">对map中的每个key-value依次调用f</td>
</tr>
</tbody>
</table>
<p>下面的代码示例演示了并发读写<code>sync.Map</code>。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;strconv&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;sync&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">m1</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">Map</span>=<span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">Map</span>{}  <span style="color:#75715e">// 要初始化
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">wg</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">WaitGroup</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#ae81ff">10</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Add</span>(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">go</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">n</span> <span style="color:#66d9ef">int</span>) {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">key</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">strconv</span>.<span style="color:#a6e22e">Itoa</span>(<span style="color:#a6e22e">n</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">m1</span>.<span style="color:#a6e22e">Store</span>(<span style="color:#a6e22e">key</span>,<span style="color:#a6e22e">n</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">res</span>,<span style="color:#a6e22e">_</span><span style="color:#f92672">:=</span><span style="color:#a6e22e">m1</span>.<span style="color:#a6e22e">Load</span>(<span style="color:#a6e22e">key</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;key为：%s，value为：%d\n&#34;</span>,<span style="color:#a6e22e">key</span>,<span style="color:#a6e22e">res</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Done</span>()
</span></span><span style="display:flex;"><span>		}(<span style="color:#a6e22e">i</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Wait</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#75715e">//报错：fatal error: concurrent map writes
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span></code></pre></div><h3 id="垃圾回收gc">
  垃圾回收(GC)
  <a class="anchor" href="#%e5%9e%83%e5%9c%be%e5%9b%9e%e6%94%b6gc">#</a>
</h3>
<h4 id="标记清除">
  标记清除
  <a class="anchor" href="#%e6%a0%87%e8%ae%b0%e6%b8%85%e9%99%a4">#</a>
</h4>
<p>此算法主要有两个主要的步骤：</p>
<p>标记(Mark phase)</p>
<p>清除(Sweep phase)</p>
<p>第一步，找出不可达的对象，然后做上标记。
第二步，回收标记好的对象。</p>
<p>操作非常简单，但是有一点需要额外注意：mark and sweep算法在执行的时候，需要程序暂停！即 stop the world。
也就是说，这段时间程序会卡在哪儿。故中文翻译成卡顿.</p>
<p>标记-清扫(Mark And Sweep)算法存在什么问题？
标记-清扫(Mark And Sweep)算法这种算法虽然非常的简单，但是还存在一些问题：</p>
<p>STW，stop the world；让程序暂停，程序出现卡顿。</p>
<p>标记需要扫描整个heap</p>
<p>清除数据会产生heap碎片
这里面最重要的问题就是：mark-and-sweep 算法会暂停整个程序。</p>
<h4 id="三色并发标记法">
  三色并发标记法
  <a class="anchor" href="#%e4%b8%89%e8%89%b2%e5%b9%b6%e5%8f%91%e6%a0%87%e8%ae%b0%e6%b3%95">#</a>
</h4>
<p>首先：程序创建的对象都标记为白色。</p>
<p>gc开始：扫描所有可到达的对象，标记为灰色</p>
<p>从灰色对象中找到其引用对象标记为灰色，把灰色对象本身标记为黑色</p>
<p>监视对象中的内存修改，并持续上一步的操作，直到灰色标记的对象不存在</p>
<p>此时，gc回收白色对象</p>
<p>最后，将所有黑色对象变为白色，并重复以上所有过程。</p>
<h4 id="插入写屏障">
  插入写屏障
  <a class="anchor" href="#%e6%8f%92%e5%85%a5%e5%86%99%e5%b1%8f%e9%9a%9c">#</a>
</h4>
<p>当一个对象引用另外一个对象时，将另外一个对象标记为灰色。</p>
<p>插入屏障仅会在堆内存中生效，不对栈内存空间生效，这是因为go在并发运行时，大部分的操作都发生在栈上，函数调用会非常频繁。数十万goroutine的栈都进行屏障保护自然会有性能问题。</p>
<p>如果一个栈对象 黑色引用白色对象，白色对象依然会被当作垃圾回收。</p>
<p><strong>因此，最后还需要对栈内存进行STW，重新rescan，确保所有引用的被引用的栈对象都不会被回收。</strong></p>
<h4 id="删除写屏障">
  删除写屏障
  <a class="anchor" href="#%e5%88%a0%e9%99%a4%e5%86%99%e5%b1%8f%e9%9a%9c">#</a>
</h4>
<p>当一个白色对象被另外一个对象解除引用时，将该被引用对象标记为灰色（白色对象被保护）</p>
<p>缺点：产生内存冗余，如果上述该白色对象没有被别的对象引用，相当于还是垃圾，但是这一轮垃圾回收并没有处理掉他。</p>
<h4 id="混写屏障">
  混写屏障
  <a class="anchor" href="#%e6%b7%b7%e5%86%99%e5%b1%8f%e9%9a%9c">#</a>
</h4>
<p>当gc进行中时，创建一个对象，按照三色标记法的步骤，对象会被标记为白色，这样新生成的对象最后会被清除掉，这样会影响程序逻辑。</p>
<p>golang引入写屏障机制，可以监控对象的内存修改，并对对象进行重新标记。</p>
<p>gc一旦开始，无论是创建对象还是对象的引用改变，都会先变为灰色。</p>
<h4 id="gc触发机制">
  GC触发机制
  <a class="anchor" href="#gc%e8%a7%a6%e5%8f%91%e6%9c%ba%e5%88%b6">#</a>
</h4>
<p>GC 的触发情况主要分为两大类，分别是：</p>
<ol>
<li>
<p>系统触发：运行时自行根据内置的条件，检查、发现到，则进行 GC 处理，维护整个应用程序的可用性。</p>
<ul>
<li>
<p>a. 使用系统监控，当超过两分钟没有产生任何GC时，强制触发 GC；</p>
</li>
<li>
<p>b.使用步调（Pacing）算法，其核心思想是控制内存增长的比例,当前内存分配达到一定比例则触发</p>
</li>
</ul>
</li>
<li>
<p>手动触发：开发者在业务代码中自行调用 runtime.GC 方法来触发 GC 行为。</p>
</li>
</ol>
</article>
 
      

      <footer class="book-footer">
        
  <div class="flex flex-wrap justify-between">





</div>



  <script>(function(){function e(e){const t=window.getSelection(),n=document.createRange();n.selectNodeContents(e),t.removeAllRanges(),t.addRange(n)}document.querySelectorAll("pre code").forEach(t=>{t.addEventListener("click",function(){if(window.getSelection().toString())return;e(t.parentElement),navigator.clipboard&&navigator.clipboard.writeText(t.parentElement.textContent)})})})()</script>


 
        
      </footer>

      
  
  <div class="book-comments">

</div>
  
 

      <label for="menu-control" class="hidden book-menu-overlay"></label>
    </div>

    
    <aside class="book-toc">
      <div class="book-toc-content">
        
  
<nav id="TableOfContents">
  <ul>
    <li>
      <ul>
        <li><a href="#go语言相关">Go语言相关</a>
          <ul>
            <li><a href="#gmp模型">GMP模型</a></li>
            <li><a href="#golang-map">Golang Map</a></li>
            <li><a href="#channel">channel</a></li>
            <li><a href="#defer函数">defer函数</a></li>
            <li><a href="#sync包">sync包</a></li>
            <li><a href="#垃圾回收gc">垃圾回收(GC)</a></li>
          </ul>
        </li>
      </ul>
    </li>
  </ul>
</nav>


 
      </div>
    </aside>
    
  </main>

  
</body>
</html>












